import java.io.Serializable;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.ExportException;

import javax.swing.JOptionPane;
import javax.swing.JTextArea;


import FlexTrade.AuctionPrimitiveData;
import FlexTrade.AuctionStates;
import FlexTrade.BidPriceData;
import FlexTrade.CustomerData;
import FlexTrade.FlexTradeUtils;
import FlexTrade.IAuction;
import FlexTrade.IMarketService;
import FlexTrade.ProviderData;
import net.jini.core.entry.Entry;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.UnknownEventException;
import net.jini.core.lease.Lease;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.transaction.CannotAbortException;
import net.jini.core.transaction.CannotCommitException;
import net.jini.core.transaction.UnknownTransactionException;
import net.jini.core.transaction.server.TransactionConstants;
import net.jini.core.transaction.server.TransactionManager;
import net.jini.core.transaction.server.TransactionParticipant;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.export.Exporter;
import net.jini.export.ProxyAccessor;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.lookup.entry.Name;

/*
 * Each object of this class refers to a auction that the customer
 * has joined. Customer will communicate with auction by this class
 */
public class Customer implements DiscoveryListener, RemoteEventListener,
						TransactionParticipant, ProxyAccessor, Serializable {

	private static final long serialVersionUID = 1L;
	//==================================================
	//STATIC
	  private static final long WAITFOR = 100000L;
	
	  private IAuction activeAuction;
	  public IMarketService market_service;
	  public CustomerData customerData;
	  private Remote proxy; 	//proxy of this customer
	  private long crashCount = 0;
	  public JTextArea txtText = null;
	  TransactionManager mgr = null;
	  private AuctionPrimitiveData auctionPrimitiveData;
	  public AuctionStates currentAuctionStates;
	  private CustomerUI customerUI;

	public Customer(CustomerData _customerData){
		customerData = _customerData;
		//lookUpMarketService();
		Exporter exporter = new BasicJeriExporter(
				TcpServerEndpoint.getInstance(0),
				new BasicILFactory());
				// export an object of this class and get proxy
		try {
			proxy = (Remote) exporter.export(this);
		} catch (ExportException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void discovered(DiscoveryEvent e) {
	}

	@Override
	public void discarded(DiscoveryEvent e) {
		// TODO Auto-generated method stub
	}

	@Override
	public void notify(RemoteEvent arg0) throws UnknownEventException,
			RemoteException {
		// TODO Auto-generated method stub
		String messageLog = "";
		String uniqueCustomerIDOfWinner = "";		//ID of customer who won the auction
		BidPriceData bidPriceData;				//information about customer who won auction and its bid price
		//get the auction which raised this event
		IAuction sourceAuction = (IAuction)arg0.getSource();
		//the final status when the event occurs
		AuctionStates finalAuctionStates = sourceAuction.getCurrentAuctionStates();
		ProviderData auctionProviderData = sourceAuction.getAuctionPrimitiveData().provider;
		float winningPrice;
		int eventID = (int) arg0.getID();		//event ID
		
		switch(eventID){
			case IAuction.E_AUCTION_FINISH:
				//get ID of customer who won the bid from remote auction object
				uniqueCustomerIDOfWinner = finalAuctionStates.highestBidCustomer.getUniqueCustomerID();
				winningPrice = finalAuctionStates.highestBidPrice;
				if(uniqueCustomerIDOfWinner.equals(customerData.getUniqueCustomerID())) {	//if you won
					messageLog = FlexTradeUtils.currentTime() + ": Congratulation. You won auction of ID:" + 
							sourceAuction.getUniqueAuctionID();
					messageLog = messageLog + " with price " + Float.toString(winningPrice);
					JOptionPane.showMessageDialog(null, messageLog, "Auction finished",JOptionPane.OK_CANCEL_OPTION);
					//tranfer money to provider
					this.credit(winningPrice, uniqueCustomerIDOfWinner, auctionProviderData.getProviderID());
				} else {	//you lost this auction
					messageLog = FlexTradeUtils.currentTime() + "Auction ID " + sourceAuction.getUniqueAuctionID() + " is finished";
					messageLog = messageLog + ". Winning price is " + Float.toString(winningPrice);
				}
				//display logging information to text area in main frame UI
				this.setText(messageLog);
				break;
			
			case IAuction.E_BID_PRICE_RECEIVED:
				//get the last bid that placed in this auction
				bidPriceData = sourceAuction.getLastBidPriceData();
				messageLog = FlexTradeUtils.currentTime() + ": Received a bid price of " + bidPriceData.getBidPrice() + " from ";
				messageLog = messageLog + "customer " + bidPriceData.customer.getUniqueCustomerID();
				messageLog = messageLog + " for aution ID " + sourceAuction.getUniqueAuctionID();
				this.setText(messageLog);
				break;

			case IAuction.E_CUSTOMER_JOIN:
				messageLog = FlexTradeUtils.currentTime() + ": A new customer has joined the auction :" +
															sourceAuction.getUniqueAuctionID();
				this.setText(messageLog);
				break;
			
			case IAuction.V_AUCTION_FINISH:
				uniqueCustomerIDOfWinner = finalAuctionStates.highestBidCustomer.getUniqueCustomerID();
				//in Vickrey, customer winning the bid would pay the second highest price
				winningPrice = finalAuctionStates.secondHighestBidPrice;
				if(uniqueCustomerIDOfWinner.equals(customerData.getUniqueCustomerID())) {	//if you won
					messageLog = FlexTradeUtils.currentTime() + ": Congratulation. You won auction :" + 
							sourceAuction.getUniqueAuctionID();
					messageLog = messageLog + " with price " + Float.toString(winningPrice); 
					JOptionPane.showMessageDialog(null, messageLog, "Auction finished",JOptionPane.OK_CANCEL_OPTION);
					this.credit(winningPrice, uniqueCustomerIDOfWinner, auctionProviderData.getProviderID());
				} else {	//if you lost this auction
					messageLog = FlexTradeUtils.currentTime() + ": Auction " + sourceAuction.getUniqueAuctionID() + " is finished";
					messageLog = ". Winning price is " + Float.toString(finalAuctionStates.highestBidPrice);
				}
				this.setText(messageLog);
				break;
			
			case IAuction.V_BID_PRICE_RECEIVED:
				//do nothing. In Vickrey auction, customer will not be informed about the other customers' information
				break;
			
			case IAuction.V_CUSTOMER_JOIN:
				//do nothing. In Vickrey auction, customer will not be informed about the other customers' information
				break;
			
			case IAuction.D_AUCTION_FINISH:
				//do nothing
				break;
			
			case IAuction.D_ACCEPTABLE_PRICE_RECEIVED:
				messageLog = FlexTradeUtils.currentTime() + ": Received new offer from auction ID:" + sourceAuction.getUniqueAuctionID();
				messageLog = messageLog + " with price of " + Float.toString(finalAuctionStates.acceptableBidPrice);
				/*messageLog = messageLog + ". Would you like to accept?";
				int reply = JOptionPane.showConfirmDialog(null, messageLog, "New Offer Received", JOptionPane.YES_NO_OPTION);
				if(reply == JOptionPane.YES_OPTION){	//accept this offer
					//check this again
					BidPriceData _bidPriceData = new BidPriceData();
					_bidPriceData.customer = this.customerData;
					_bidPriceData.bidPrice = finalAuctionStates.acceptableBidPrice;
					sourceAuction.receivePriceAcceptanceFromCustomer(_bidPriceData);
				} else {
					messageLog = FlexTradeUtils.currentTime() + ": Received new offer of " +  Float.toString(finalAuctionStates.acceptableBidPrice);
					messageLog = messageLog + " for auction " + sourceAuction.getUniqueAuctionID();
				}*/
				
				//refresh the price value in auction table of main UI
				System.out.println(messageLog);
				this.setText(messageLog);
				customerUI.refreshAuctionsTable();
				break;
		
			case IAuction.D_CUSTOMER_JOIN:
				//do nothing
				break;

			case IAuction.D_CUSTOMER_ACCEPT:
				uniqueCustomerIDOfWinner = finalAuctionStates.highestBidCustomer.getUniqueCustomerID();
				winningPrice = finalAuctionStates.acceptableBidPrice;
				messageLog = FlexTradeUtils.currentTime() + ": Customer " + uniqueCustomerIDOfWinner + " has accepted ";
				messageLog = messageLog + " offer of " + Float.toString(winningPrice) + " from provider ";
				messageLog = messageLog + auctionProviderData.getProviderID();
				//transfer money here
				this.setText(messageLog);
				this.credit(winningPrice, uniqueCustomerIDOfWinner, auctionProviderData.getProviderID());
				break;

			case IAuction.CANNOT_TRANSFER_PAYMENT:
				uniqueCustomerIDOfWinner = finalAuctionStates.highestBidCustomer.getUniqueCustomerID();
				//only show log about payment to customer/provider involve in this transaction
				if(uniqueCustomerIDOfWinner.equals(customerData.getUniqueCustomerID()) == false){
					break;
				}
				int t_auctionType = sourceAuction.getAuctionPrimitiveData().auctionType;
				if(t_auctionType == IAuction.ENGLISH_AUCTION_TYPE){
					winningPrice = finalAuctionStates.highestBidPrice;
				} else if(t_auctionType == IAuction.VICKREY_AUCTION_TYPE){
					winningPrice = finalAuctionStates.secondHighestBidPrice;
				} else if(t_auctionType == IAuction.DUTCH_AUCTION_TYPE) {
					winningPrice = finalAuctionStates.acceptableBidPrice;
				} else {
					winningPrice = 0;
				}
				messageLog = FlexTradeUtils.currentTime() + ": Error. Payment of " + Float.toString(winningPrice) + " cannot be transfered ";
				messageLog = messageLog + "from customer " + uniqueCustomerIDOfWinner + " bank account to provider ";
				messageLog = messageLog + auctionProviderData.getProviderID() + " bank account";
				this.setText(messageLog);					
				break;
			
			case IAuction.PAYMENT_TRANSFERED_SUCCESSFULLY:
				uniqueCustomerIDOfWinner = finalAuctionStates.highestBidCustomer.getUniqueCustomerID();
				//only show log about payment to customer/provider involve in this transaction
				if(uniqueCustomerIDOfWinner.equals(customerData.getUniqueCustomerID()) == false){
					break;
				}
				t_auctionType = sourceAuction.getAuctionPrimitiveData().auctionType;
				if(t_auctionType == IAuction.ENGLISH_AUCTION_TYPE){
					winningPrice = finalAuctionStates.highestBidPrice;
				} else if(t_auctionType == IAuction.VICKREY_AUCTION_TYPE){
					winningPrice = finalAuctionStates.secondHighestBidPrice;
				} else if(t_auctionType == IAuction.DUTCH_AUCTION_TYPE) {
					winningPrice = finalAuctionStates.acceptableBidPrice;
				} else {
					winningPrice = 0;
				}
				messageLog = FlexTradeUtils.currentTime() + ": Payment of " + Float.toString(winningPrice) + " has been transfered successfully ";
				messageLog = messageLog + "from customer " + uniqueCustomerIDOfWinner + " bank account to provider ";
				messageLog = messageLog + auctionProviderData.getProviderID() + " bank account";
				this.setText(messageLog);
				break;
				
			case(IAuction.D_AUCTION_TIMEOUT):
				//do nothing
				break;
			
			default:
				System.out.println("Invalid event ID occurs" + Integer.toString(eventID));
				return;
		}
	}
	
	/*
	 * call this method when customer decides to accept offer from provider
	 * for Dutch auction.
	 */
	public void acceptPriceFromProvider(BidPriceData _bidPriceData){
		try {
			this.activeAuction.receivePriceAcceptanceFromCustomer(_bidPriceData);
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/*
	 * call this method when customer place a bid to English/Vickrey auction 
	 * with uniqueAuctionID
	 */
	public void placeNewBidPrice(String uniqueAuctionID, int auctionType, float _bidPrice){
		//create a BidPriceData object
		BidPriceData bidPriceData = new BidPriceData();
		bidPriceData.setCustomer(this.customerData);
		bidPriceData.setBidPrice(_bidPrice);
		try {
			this.activeAuction.receiveBidPriceFromCustomer(bidPriceData);
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/*
	 * call this method when customer wants to join auction with uniqueAuctionID
	 * i.e retrieve remote object of this auction
	 */
	public void joinRequestToAuction(String uniqueAuctionID) {
		ServiceDiscoveryManager clientMgr = null;
		try {
			LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
																	null, // unicast locators
																	null); // DiscoveryListener
			clientMgr = new ServiceDiscoveryManager(mgr, new LeaseRenewalManager());
		} catch(Exception e) {
			e.printStackTrace();
		}
		//Create template to find FlexTrade service by name
		Entry[] serverAttributes = new Entry[1];
	    serverAttributes[0] = new Name (uniqueAuctionID);
	    ServiceTemplate template = 
	    		  new ServiceTemplate (null, null, serverAttributes);
		ServiceItem item = null;
		// Try to find the service, blocking until timeout if necessary
		try {
			item = clientMgr.lookup(template,
										null, // no filter
										Customer.WAITFOR); // timeout
		} catch(Exception e) {
			e.printStackTrace();
			//System.exit(1);
		}
		if (item == null) {
			// couldn't find a service in time
			System.out.println("No auction service.");
			return;
		}
		// Get the service
		this.activeAuction = (IAuction) item.service;
		try{
			activeAuction.addRemoteListener((RemoteEventListener)proxy);
			currentAuctionStates = activeAuction.getCurrentAuctionStates();
		} catch(RemoteException e){
			e.printStackTrace();
		}
		if (this.activeAuction == null) {
			System.out.println("Auction service is null.");
			return;
			//System.exit(1);
		}
	}
	
	//do the transaction that transfer payment to provider bank account
	public void credit(float amount, String customerID, String providerID){
		mgr = findTxnMgr();
		TransactionManager.Created tcs = null;
		try {
			tcs = mgr.create(Lease.FOREVER);
		} catch(java.rmi.RemoteException e) {
			e.printStackTrace();
		} catch(net.jini.core.lease.LeaseDeniedException e) {
			e.printStackTrace();
		}

		long transactionID = tcs.id;
		try {
			mgr.join(transactionID, (TransactionParticipant)proxy, crashCount);
		} catch(net.jini.core.transaction.UnknownTransactionException e) {
			e.printStackTrace();
		} catch(java.rmi.RemoteException e) {
			e.printStackTrace();
		} catch(net.jini.core.transaction.server.CrashCountException e) {
			e.printStackTrace();
		} catch(net.jini.core.transaction.CannotJoinException e) {
			e.printStackTrace();
		}
		new LeaseRenewalManager().renewUntil(tcs.lease,
												Lease.FOREVER,
												null);		
		//if not successfully transfer in auction object
		boolean paymentTransfer = false;
		try {
			paymentTransfer = activeAuction.paymentTransfer(amount, customerID, 
															providerID, mgr, transactionID);
		} catch (RemoteException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		if(!paymentTransfer){	//cannot make payment transfered
				try {
					mgr.abort(transactionID);		//abort transaction
				} catch (UnknownTransactionException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (CannotAbortException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		} else {	//make payment ==> commit transaction

				try {
					if(mgr.getState(transactionID) != TransactionConstants.ABORTED){
						mgr.commit(transactionID);
					}
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		}
		
	}
	
	private TransactionManager findTxnMgr() {
		ServiceDiscoveryManager clientMgr = null;
		try {
			LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
																	null, // unicast locators
																	null); // DiscoveryListener
			clientMgr = new ServiceDiscoveryManager(mgr, new LeaseRenewalManager());
		} catch(Exception e) {
			e.printStackTrace();
			return null;
			//System.exit(1);
		}
		Class [] classes = new Class[] {TransactionManager.class};
		ServiceTemplate template = new ServiceTemplate(null, classes,null);
		ServiceItem item = null;
		// Try to find the service, blocking until timeout if necessary
		try {
			item = clientMgr.lookup(template,
										null, // no filter
										Customer.WAITFOR); // timeout
		} catch(Exception e) {
			e.printStackTrace();
			return null;
			//System.exit(1);
		}
		if (item == null) {
			// couldn't find a service in time
			System.out.println("No transaction manager service.");
			return null;
			//System.exit(1);
		}
		// Get the service
		TransactionManager mgr = (TransactionManager) item.service;
		if (mgr == null) {
			System.out.println("Transaction Manager is null.");
			return null;
			//System.exit(1);
		}
		return mgr;
	}

	@Override
	public void abort(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		System.out.println("Customer object is aborted");
		
	}

	@Override
	public void commit(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		System.out.println("Customer object is committed");
	}

	@Override
	public int prepare(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		System.out.println("Customer is prepared");
		return TransactionConstants.PREPARED;
	}

	@Override
	public int prepareAndCommit(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		int result = prepare(arg0, arg1);
		if(result == TransactionConstants.PREPARED) {
			commit(arg0, arg1);
			result = TransactionConstants.COMMITTED;
		}
		System.out.println("Customer is prepared and committed");
		return result;
	}
	
	//Get reference to text area in main form
	public void setTextField(javax.swing.JTextArea txt){
		this.txtText = txt;
	}
	
	//Print message to textfield in main frame UI
	public void setText(String message){
		txtText.append("\n" + message);
	}
	
	public void setAuctionPrimitiveData(AuctionPrimitiveData _auctionPrimitiveData){
		this.auctionPrimitiveData = _auctionPrimitiveData;
	}
	
	public void setCustomerUI(CustomerUI _customerUI){
		this.customerUI = _customerUI;
	}

	@Override
	public Object getProxy() {
		// TODO Auto-generated method stub
		return null;
	}
	
	static public IAuction lookUpAuction(String _uniqueAuctionID){
		ServiceDiscoveryManager clientMgr = null;
		try {
			LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
																	null, // unicast locators
																	null); // DiscoveryListener
			clientMgr = new ServiceDiscoveryManager(mgr, new LeaseRenewalManager());
		} catch(Exception e) {
			e.printStackTrace();
		}
		//Create template to find FlexTrade service by name		
		Entry[] serverAttributes = new Entry[1];
	    serverAttributes[0] = new Name (_uniqueAuctionID);
	    ServiceTemplate template = 
	    		  new ServiceTemplate (null, null, serverAttributes);

		ServiceItem item = null;
		// Try to find the service, blocking until timeout if necessary
		try {
			item = clientMgr.lookup(template,
										null, // no filter
										100000); // timeout
		} catch(Exception e) {
			e.printStackTrace();
		}
		if (item == null) {
			System.out.println("No auction service.");
		}
		// Get the service
		IAuction t_auction = (IAuction) item.service;
		return t_auction;
	}

}


/*
 * ========================================================================================
 * REMOVE CODE
 */
//Setup LookupDiscoveryManager(LUS) and find the market service

/*
public void lookUpMarketService(){
	ServiceDiscoveryManager clientMgr = null;
	try {
		LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
																null, // unicast locators
																null); // DiscoveryListener
		clientMgr = new ServiceDiscoveryManager(mgr, new LeaseRenewalManager());
	} catch(Exception e) {
		e.printStackTrace();
	}
	//Create template to find FlexTrade service by name		
	Entry[] serverAttributes = new Entry[1];
    serverAttributes[0] = new Name (Customer.FLEXTRADE_SERVICE_NAME);
    ServiceTemplate template = 
    		  new ServiceTemplate (null, null, serverAttributes);

	ServiceItem item = null;
	// Try to find the service, blocking until timeout if necessary
	try {
		item = clientMgr.lookup(template,
									null, // no filter
									100000); // timeout
	} catch(Exception e) {
		e.printStackTrace();
	}
	if (item == null) {
		System.out.println("No market service.");
	}
	// Get the service
	market_service = (IMarketService) item.service;
	if (market_service == null) {
		System.out.println("Market service is null.");
	}
}
*/